package com.csdj.wenjun.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.csdj.wenjun.common.WebResult;
import com.csdj.wenjun.entity.Qiandao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService extends MessageListenerAdapter {
    private Logger log = LoggerFactory.getLogger("redisService");

    @Resource
    private RedisTemplate<String,Object> objectRedisTemplate;

    public void setString(String key,String value,int second){
        objectRedisTemplate.opsForValue().set(key,value,second,TimeUnit.SECONDS);
    }
    public Long setList(String key,String value){
        return objectRedisTemplate.opsForList().leftPush(key,value);
    }
    public long incr(String key){
        return objectRedisTemplate.opsForValue().increment(key);
    }
    public List getList(String key){
        List list=null;
        try {
             list = objectRedisTemplate.opsForList().range(key,0,-1);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
        return list;
    }
    public String getString(String key){
        Object o = objectRedisTemplate.opsForValue().get(key);
        if(o == null)return null;
        return o.toString();
    }


    public void setObject(String key,Object obj,int second){
        objectRedisTemplate.opsForValue().set(key,obj,second,TimeUnit.SECONDS);
    }

    public <T> T getObject(String key){
        Object o = objectRedisTemplate.opsForValue().get(key);
        if(o == null)return null;
        return (T)o;
    }

    public <T extends Object> T getObject(String key,Class clazz){
        Object o = objectRedisTemplate.opsForValue().get(key);
        if(o == null)return null;
        if(clazz.isInstance(o))
            if(o.getClass().getName().equalsIgnoreCase(clazz.getName())){
                return (T)o;
            }
        log.error("{}的值不是{}的实例",key,clazz.getName());
        return null;
    }

    /**
     * redis订阅消息处理
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
//        super.onMessage(message, pattern);
        log.info("channel:{} message:{}",new String(message.getChannel()),new String(message.getBody()));
    }


    public Qiandao selectqiandao(Integer userId, String dateStr){
        Qiandao qiandao=new Qiandao();
        Date date = getDate(dateStr);
        // 获取日期对应的天数，多少号
        String signKeylist = buildSignKeylist(userId, date);
        int day = DateUtil.dayOfMonth(date) - 1; // 从 0 开始
        // 构建 Redis Key
        String signKey = buildSignKey(userId, date);
        Date today = new Date();
        int continuous = getContinuousSignCount(userId, today);
        long count = getSumSignCount(userId, today);
        qiandao.setLianxu( continuous);
        qiandao.setCount((int) count);
        qiandao.setQiandaori(getList(signKeylist));
        return qiandao;
    }


    /**
     * 用户签到，可以补签
     *
     * @param userId  用户ID
     * @param dateStr 查询的日期，默认当天 yyyy-MM-dd
     * @return 连续签到次数和总签到次数
     */
    public WebResult doSign(Integer userId, String dateStr) {
        // 获取日期
        Qiandao qiandao=new Qiandao();
        Date date = getDate(dateStr);
        // 获取日期对应的天数，多少号
        int day = DateUtil.dayOfMonth(date) - 1; // 从 0 开始
        // 构建 Redis Key
        String signKey = buildSignKey(userId, date);
        String signKeylist = buildSignKeylist(userId, date);
        System.out.println(signKey);
        // 查看指定日期是否已签到
        boolean isSigned = objectRedisTemplate.opsForValue().getBit(signKey, day);
        System.out.println(isSigned);
        if (isSigned) {
            Date today = new Date();
            int continuous = getContinuousSignCount(userId, today);
            long count = getSumSignCount(userId, today);
            qiandao.setLianxu( continuous);
            qiandao.setCount((int) count);
            qiandao.setQiandaori(getList(signKeylist));
            System.out.println(qiandao.getQiandaori());
            return new WebResult(400,qiandao);
        }
        // 签到
        objectRedisTemplate.opsForValue().setBit(signKey, day, true);
        // 根据当前日期统计签到次数
        Date today = new Date();
        // 统计连续签到次数
        int continuous = getContinuousSignCount(userId, today);
        // 统计总签到次数
        long count = getSumSignCount(userId, today);
        objectRedisTemplate.opsForList().leftPush(signKeylist,String.valueOf((day+1)));
        qiandao.setLianxu( continuous);
        qiandao.setCount((int) count);
        qiandao.setQiandaori(getList(signKeylist));
        System.out.println("continuous"+qiandao.getQiandaori());
        return new WebResult(200,qiandao);
    }

    /**
     * 统计连续签到次数
     *
     * @param userId 用户ID
     * @param date   查询的日期
     * @return
     */
    private int getContinuousSignCount(Integer userId, Date date) {
        // 获取日期对应的天数，多少号，假设是 31
        int dayOfMonth = DateUtil.dayOfMonth(date);
        // 构建 Redis Key
        String signKey = buildSignKey(userId, date);
        // e.g. bitfield user:sign:5:202103 u31 0
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                .valueAt(0);
        // 获取用户从当前日期开始到 1 号的所有签到状态
        List<Long> list = objectRedisTemplate.opsForValue().bitField(signKey, bitFieldSubCommands);
        if (list == null || list.isEmpty()) {
            return 0;
        }
        // 连续签到计数器
        int signCount = 0;
        long v = list.get(0) == null ? 0 : list.get(0);
        // 位移计算连续签到次数
        for (int i = dayOfMonth; i > 0; i--) {// i 表示位移操作次数
            // 右移再左移，如果等于自己说明最低位是 0，表示未签到
            if (v >> 1 << 1 == v) {
                // 用户可能当前还未签到，所以要排除是否是当天的可能性
                // 低位 0 且非当天说明连续签到中断了
                if (i != dayOfMonth) break;
            } else {
                // 右移再左移，如果不等于自己说明最低位是 1，表示签到
                signCount++;
            }
            // 右移一位并重新赋值，相当于把最低位丢弃一位然后重新计算
            v >>= 1;
        }
        return signCount;
    }

    /**
     * 统计总签到次数
     *
     * @param userId 用户ID
     * @param date   查询的日期
     * @return
     */
    private Long getSumSignCount(Integer userId, Date date) {
        // 构建 Redis Key
        String signKey = buildSignKey(userId, date);
        // e.g. BITCOUNT user:sign:5:202103
        return (Long) objectRedisTemplate.execute(
                (RedisCallback<Long>) con -> con.bitCount(signKey.getBytes())
        );
    }

    /**
     * 获取日期
     *
     * @param dateStr yyyy-MM-dd
     * @return
     */
    private Date getDate(String dateStr) {
        return StrUtil.isBlank(dateStr) ?
                new Date() : DateUtil.parseDate(dateStr);
    }

    /**
     * 构建 Redis Key - user:sign:userId:yyyyMM
     *
     * @param userId 用户ID
     * @param date   日期
     * @return
     */
    private String buildSignKey(Integer userId, Date date) {
        return String.format("user:sign:%d:%s", userId,
                DateUtil.format(date, "yyyyMM"));
    }
    private String buildSignKeylist(Integer userId, Date date) {
        return String.format("user_sign_%d_%s", userId,
                DateUtil.format(date, "yyyyMM"));
    }
}